/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.builder;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.jfab.core.analyzer.ClassAnalyzer;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGConstructorList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGInitializationBlockList;
import org.jfab.core.collection.CGMethodList;
import org.jfab.core.collection.CGParameterList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.MethodBodyList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.comparator.CGConstructorComparator;
import org.jfab.core.factory.CGAnnotationFactory;
import org.jfab.core.factory.CGClassFactory;
import org.jfab.core.factory.CGConstructorFactory;
import org.jfab.core.factory.CGFieldFactory;
import org.jfab.core.factory.CGMethodFactory;
import org.jfab.core.factory.CGParameterFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGAnnotation;
import org.jfab.core.model.CGClass;
import org.jfab.core.model.CGConstructor;
import org.jfab.core.model.CGField;
import org.jfab.core.model.CGMethod;
import org.jfab.core.model.CGParameter;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.model.Decorator;
import org.jfab.core.model.Modifier;

/**
 * Provides a decorator class builder.
 */
public class DecoratorCGClassBuilder implements CGClassBuilder<Decorator>
{
    /** Annotation factory. */
    private final CGAnnotationFactory _annotationFactory;

    /** Class analyzer. */
    private final ClassAnalyzer _classAnalyzer;

    /** Class factory. */
    private final CGClassFactory _classFactory;

    /** Constructor comparator. */
    private final Comparator<CGConstructor> _constructorComparator;

    /** Constructor factory. */
    private final CGConstructorFactory _constructorFactory;

    /** Field comparator. */
    private final Comparator<CGField> _fieldComparator;

    /** Field factory. */
    private final CGFieldFactory _fieldFactory;

    /** Method comparator. */
    private final Comparator<CGMethod> _methodComparator;

    /** Method factory. */
    private final CGMethodFactory _methodFactory;

    /** Parameter factory. */
    private final CGParameterFactory _parameterFactory;

    /** String factory. */
    private final StringFactory _stringFactory;

    /** Code generator type utilities. */
    private final CGTypeUtilities _cgTypeUtils;

    /**
     * Construct this object with the given parameters.
     * 
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param stringFactory String factory.
     * @param annotationFactory Annotation factory.
     * @param parameterFactory Parameter factory.
     * @param fieldFactory Field factory.
     * @param fieldComparator Field comparator.
     * @param constructorFactory Constructor factory.
     * @param constructorComparator Constructor comparator.
     * @param methodFactory Method factory.
     * @param methodComparator Method comparator.
     * @param classFactory Class factory.
     * @param classAnalyzer Class analyzer.
     */
    public DecoratorCGClassBuilder(final CGTypeUtilities cgTypeUtils,
            final StringFactory stringFactory,
            final CGAnnotationFactory annotationFactory,
            final CGParameterFactory parameterFactory,
            final CGFieldFactory fieldFactory,
            final Comparator<CGField> fieldComparator,
            final CGConstructorFactory constructorFactory,
            final CGConstructorComparator constructorComparator,
            final CGMethodFactory methodFactory,
            final Comparator<CGMethod> methodComparator,
            final CGClassFactory classFactory, final ClassAnalyzer classAnalyzer)
    {
        _cgTypeUtils = cgTypeUtils;
        _stringFactory = stringFactory;
        _annotationFactory = annotationFactory;
        _parameterFactory = parameterFactory;
        _fieldFactory = fieldFactory;
        _fieldComparator = fieldComparator;
        _constructorFactory = constructorFactory;
        _constructorComparator = constructorComparator;
        _methodFactory = methodFactory;
        _methodComparator = methodComparator;
        _classFactory = classFactory;
        _classAnalyzer = classAnalyzer;
    }

    @Override
    public final CGClass build(final Decorator cgDecorator)
    {
        final CGTypeList referencedTypes = null;
        final CGAnnotationList annotations = new CGAnnotationList(
                cgDecorator.getAnnotations());
        final String packageName = cgDecorator.getPackageName();
        final CommentList commentLines = new CommentList(
                Collections.singletonList("Provides a " + cgDecorator.getName()
                        + " decorator."));
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final String className = cgDecorator.getName();
        final List<String> typeParameters = null;
        CGType extendsType = cgDecorator.getExtendsType();
        final CGType decoratedType = cgDecorator.getDecoratedType();
        final CGTypeList implementsInterfaces = new CGTypeList(
                cgDecorator.getImplementsInterfaces());

        if (_cgTypeUtils.getType(decoratedType).isInterface())
        {
            implementsInterfaces.add(decoratedType);
        }
        else
        {
            extendsType = decoratedType;
        }

        final CGInitializationBlockList initializationBlocks = null;
        final CGFieldList fields = createFields(cgDecorator);
        final CGConstructorList constructors = createConstructors(cgDecorator);
        final CGMethodList methods = createMethods(cgDecorator);

        // Sort
        Collections.sort(fields, getFieldComparator());
        Collections.sort(constructors, getConstructorComparator());
        Collections.sort(methods, getMethodComparator());

        return getClassFactory().createClass(referencedTypes, packageName,
                commentLines, annotations, modifiers, className,
                typeParameters, extendsType, implementsInterfaces,
                initializationBlocks, constructors, fields, methods);
    }

    /**
     * @param cgDecorator Decorator.
     * 
     * @return new code generator constructors.
     */
    private CGConstructorList createConstructors(final Decorator cgDecorator)
    {
        final CGConstructorList answer = new CGConstructorList();

        final String className = cgDecorator.getName();

        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList();
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGParameterList parameters = new CGParameterList();
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList();

        final CGParameterFactory parameterFactory = getParameterFactory();
        final StringFactory stringFactory = getStringFactory();
        commentLines.add("Construct this object with the given parameter.");

        final CGType decoratedType = cgDecorator.getDecoratedType();
        final String parameterName = createPropertyName();
        final String fieldName = createFieldName(cgDecorator);
        final String parameterComment = stringFactory
                .createCommentFromPropertyName(parameterName);
        commentLines.add("@param " + parameterName + " " + parameterComment);

        final CGParameter parameter = parameterFactory.createParameter(
                decoratedType, parameterName);
        parameters.add(parameter);
        methodBodyLines.add(fieldName + " = " + parameterName + ";");

        final CGConstructorFactory constructorFactory = getConstructorFactory();

        final CGConstructor constructor = constructorFactory.createConstructor(
                referencedTypes, commentLines, annotations, modifiers,
                className, parameters, throwsTypes, methodBodyLines);
        answer.add(constructor);

        return answer;
    }

    /**
     * @param cgDecorator Decorator.
     * 
     * @return a new field name.
     */
    private String createFieldName(final Decorator cgDecorator)
    {
        final StringFactory stringFactory = getStringFactory();
        final String propertyName = createPropertyName();

        return stringFactory.createMemberVariableName(propertyName);
    }

    /**
     * @param cgDecorator Decorator.
     * 
     * @return new code generator fields.
     */
    private CGFieldList createFields(final Decorator cgDecorator)
    {
        final CGFieldList answer = new CGFieldList();

        final String parameterName = createPropertyName();

        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList();
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList();
        modifiers.add(Modifier.PRIVATE.getName());
        modifiers.add(Modifier.FINAL.getName());
        final CGType fieldType = cgDecorator.getDecoratedType();
        final String fieldName = createFieldName(cgDecorator);
        final String initialValue = null;
        final Boolean isGetter = null;
        final Boolean isSetter = null;
        final Boolean isUsedInIdentity = Boolean.TRUE;

        final StringFactory stringFactory = getStringFactory();
        commentLines.add(stringFactory
                .createCommentFromPropertyName(parameterName));

        final CGFieldFactory fieldFactory = getFieldFactory();
        final CGField field = fieldFactory.createField(referencedTypes,
                commentLines, annotations, modifiers, fieldType, fieldName,
                initialValue, isGetter, isSetter, isUsedInIdentity);
        answer.add(field);

        return answer;
    }

    /**
     * @param cgDecorator Value.
     * 
     * @return new code generator methods.
     */
    private CGMethodList createMethods(final Decorator cgDecorator)
    {
        final CGMethodList answer = new CGMethodList();

        // Add a delegate method for each method in decoratedType.
        Class<?> aClass = _cgTypeUtils.getType(cgDecorator.getDecoratedType())
                .getRawClass();
        answer.addAll(createMethodsForClass(cgDecorator, aClass));

        while (aClass.getSuperclass() != null)
        {
            aClass = aClass.getSuperclass();

            if (aClass != Object.class)
            {
                answer.addAll(createMethodsForClass(cgDecorator, aClass));
            }
        }

        return answer;
    }

    /**
     * @param cgDecorator Code generator decorator.
     * @param aClass Class.
     * 
     * @return delegate methods for the given class.
     */
    private CGMethodList createMethodsForClass(final Decorator cgDecorator,
            final Class<?> aClass)
    {
        final CGMethodList answer = new CGMethodList();

        final String javaSource = null;
        final ClassAnalyzer classAnalyzer = getClassAnalyzer();
        final CGClass cgClass = classAnalyzer.analyze(aClass, javaSource);
        final CGMethodFactory methodFactory = getMethodFactory();

        for (final CGMethod cgMethod : cgClass.getMethods())
        {
            final CommentList commentLines = new CommentList(
                    cgMethod.getCommentLines());
            final CGAnnotationList annotations = new CGAnnotationList(
                    cgMethod.getAnnotations());

            final CGAnnotation overrideAnnotation = getAnnotationFactory()
                    .createOverrideAnnotation();

            if (!annotations.contains(overrideAnnotation))
            {
                annotations.add(overrideAnnotation);
            }

            final ModifierList modifiers = new ModifierList(
                    cgMethod.getModifiers());

            if (modifiers.isAbstract())
            {
                modifiers.remove(Modifier.ABSTRACT.getName());
            }

            final CGType returnType = cgMethod.getReturnType();
            final String fieldName = createFieldName(cgDecorator);
            final String methodName = cgMethod.getName();

            final CGParameterList parameters = new CGParameterList(
                    cgMethod.getParameters());

            final CGMethod delegateMethod = methodFactory.createDelegateMethod(
                    commentLines, annotations, modifiers, returnType,
                    methodName, parameters, fieldName);
            answer.add(delegateMethod);
        }

        return answer;
    }

    /**
     * @return a new property name.
     */
    private String createPropertyName()
    {
        return "component";
    }

    /**
     * @return the annotationFactory
     */
    private CGAnnotationFactory getAnnotationFactory()
    {
        return _annotationFactory;
    }

    /**
     * @return the classAnalyzer
     */
    private ClassAnalyzer getClassAnalyzer()
    {
        return _classAnalyzer;
    }

    /**
     * @return the classFactory
     */
    private CGClassFactory getClassFactory()
    {
        return _classFactory;
    }

    /**
     * @return the constructorComparator
     */
    private Comparator<CGConstructor> getConstructorComparator()
    {
        return _constructorComparator;
    }

    /**
     * @return the constructorFactory
     */
    private CGConstructorFactory getConstructorFactory()
    {
        return _constructorFactory;
    }

    /**
     * @return the fieldComparator
     */
    private Comparator<CGField> getFieldComparator()
    {
        return _fieldComparator;
    }

    /**
     * @return the fieldFactory
     */
    private CGFieldFactory getFieldFactory()
    {
        return _fieldFactory;
    }

    /**
     * @return the methodComparator
     */
    private Comparator<CGMethod> getMethodComparator()
    {
        return _methodComparator;
    }

    /**
     * @return the methodFactory
     */
    private CGMethodFactory getMethodFactory()
    {
        return _methodFactory;
    }

    /**
     * @return the parameterFactory
     */
    private CGParameterFactory getParameterFactory()
    {
        return _parameterFactory;
    }

    /**
     * @return the stringFactory
     */
    private StringFactory getStringFactory()
    {
        return _stringFactory;
    }
}
